
#include <BSerialPort>
#include "member_BSerialPort.h"

using namespace BWE;

#define member					(*(member_BSerialPort*)_ptr)
#define member_allocate()		_ptr = new member_BSerialPort(this)
#define member_release()		delete (member_BSerialPort*)_ptr

BSerialPort::BSerialPort()
{
	member_allocate();
}
BSerialPort::~BSerialPort()
{
	close();
	member_release();
}

void BSerialPort::setPortName(const BString& portName)
{
	if (member.portName != portName)
	{
		member.portName = portName;
		close();
	}
}
const BString& BSerialPort::portName() const
{
	return member.portName;
}

bool BSerialPort::setBaudRate(BaudRate baudRate)
{
	if (member.baudRate != baudRate)
	{
		member.baudRate = baudRate;
		member.freshComState();
		return true;
	}
	return false;
}
BSerialPort::BaudRate BSerialPort::baudRate() const
{
	return member.baudRate;
}

bool BSerialPort::setDataBits(DataBits dataBits)
{
	if (member.dataBits != dataBits)
	{
		member.dataBits = dataBits;
		member.freshComState();
	}
	return true;
}
BSerialPort::DataBits BSerialPort::dataBits() const
{
	return member.dataBits;
}

bool BSerialPort::setDirection(Direction direction)
{
	if (member.direction != direction)
	{
		member.direction = direction;
		member.freshComState();
	}
	return true;
}
BSerialPort::Direction BSerialPort::direction() const
{
	return member.direction;
}

bool BSerialPort::setFlowControl(FlowControl flowControl)
{
	if (member.flowControl != flowControl)
	{
		member.flowControl = flowControl;
		member.freshComState();
	}
	return true;
}
BSerialPort::FlowControl BSerialPort::flowControl() const
{
	return member.flowControl;
}

bool BSerialPort::setParity(Parity parity)
{
	if (member.parity != parity)
	{
		member.parity = parity;
		member.freshComState();
	}
	return true;
}
BSerialPort::Parity BSerialPort::parity() const
{
	return member.parity;
}

bool BSerialPort::setStopBits(StopBits stopBits)
{
	if (member.stopBits != stopBits)
	{
		member.stopBits = stopBits;
		member.freshComState();
	}
	return true;
}
BSerialPort::StopBits BSerialPort::stopBits() const
{
	return member.stopBits;
}

bool BSerialPort::open(IOMode mode)
{
	close();
	if (member.portName.empty())
		return false;

	BString name("\\\\.\\");
	name += member.portName;
#ifdef linux
	member.hcom = ::open(name.cstr(), O_RDWR | O_NOCTTY | O_NONBLOCK);
	if (member.hcom == -1)
	{
		member.checkError();
		return false;
	}
	if (isatty(member.hcom) == 0)
		const char* cstr = "standard input is not a terminal device\n";
	else
		const char* cstr = "isatty success!\n";
#endif

#ifdef _WIN32
	DWORD access = 0;
	if (mode == IO_Read)
		access = GENERIC_READ;
	if (mode == IO_Write)
		access = GENERIC_WRITE;
	if (mode == IO_ReadWrite)
		access = GENERIC_READ | GENERIC_WRITE;

	member.hcom = CreateFileA(name.cstr(), access, 0, 0, OPEN_EXISTING, 0, NULL);
	if (member.hcom == INVALID_HANDLE_VALUE)
	{
		member.checkError();
		return false;
	}
	member.freshComState();

	COMMTIMEOUTS timeouts = { 0 };
	timeouts.ReadIntervalTimeout = 50;
	timeouts.ReadTotalTimeoutConstant = 100;
	timeouts.ReadTotalTimeoutMultiplier = 10;
	timeouts.WriteTotalTimeoutConstant = 100;
	timeouts.WriteTotalTimeoutMultiplier = 10;

	if (!SetCommTimeouts(member.hcom, &timeouts))
	{
		perror("setupTimeouts failed");
		return false;
	}
#endif
	member.mode = mode;
	return true;
}
bool BSerialPort::isOpened() const
{
#ifdef linux
	return member.hcom > -1;
#endif
#ifdef _WIN32
	return member.hcom != INVALID_HANDLE_VALUE;
#endif
}
void BSerialPort::close()
{
	if (!isOpened())
		return;
#ifdef linux
	::close(member.hcom);
	member.hcom = -1;
	member.mode = IO_None;
#endif
#ifdef _WIN32
	CloseHandle(member.hcom);
	member.hcom = INVALID_HANDLE_VALUE;
	member.mode = IO_None;
#endif
}

int BSerialPort::bytesReady() const
{
#ifdef linux
	ssize_t readSize = ::read(member.hcom, 0, 0);
	return readSize;
#endif
#ifdef _WIN32
	DWORD errors;
	COMSTAT comStat;
	if (!ClearCommError(member.hcom, &errors, &comStat))
	{
		perror("ClearCommError failed in DataAvailable");
		return 0;
	}
	return comStat.cbInQue;
#endif
}
int BSerialPort::read(void* data, int size)
{
#ifdef linux
	ssize_t readSize = ::read(member.hcom, data, size);
	return readSize;
#endif
#ifdef _WIN32
	DWORD readSize = 0;
	BOOL ret = ReadFile(member.hcom, data, size, &readSize, 0);
	if (!ret)
	{
		member.checkError();
	}
	return readSize;
#endif
}
int BSerialPort::read(BByteArray& bytes, int size)
{
	if (size < 0)
		size = bytes.size();
	if (bytes.size() < size)
		bytes.resize(size);
	return read(bytes.data(), size);
}
int BSerialPort::read(BBuffer& buffer, int size)
{
	if (size < 0)
		size = buffer.size();
	if (buffer.size() < size)
		buffer.resize(size);
	return read(buffer.bytes().data(), size);
}

int BSerialPort::write(const void* data, int size)
{
	if (isOpened() && member.mode & IO_Write)
	{
#ifdef linux
		ssize_t writeSize = ::write(member.hcom, data, size);
		return writeSize;
#endif
#ifdef _WIN32
		DWORD writeSize = 0;
		BOOL ret = WriteFile(member.hcom, data, size, &writeSize, 0);
		if (!ret)
		{
			member.checkError();
			return -1;
		}
		return writeSize;
#endif
	}
	return -1;
}
int BSerialPort::write(const BByteArray& bytes)
{
	if (isOpened() && member.mode & IO_Write)
	{
#ifdef linux
		ssize_t writeSize = ::write(member.hcom, bytes.data(), bytes.size());
		return writeSize;
#endif
#ifdef _WIN32
		DWORD writeSize = 0;
		BOOL ret = WriteFile(member.hcom, bytes.data(), bytes.size(), &writeSize, 0);
		if (!ret)
		{
			member.checkError();
			return -1;
		}
		return writeSize;
#endif
	}
	return -1;
}
int BSerialPort::write(const BBuffer& buffer)
{
	if (isOpened() && member.mode & IO_Write)
	{
#ifdef linux
		ssize_t writeSize = ::write(member.hcom, buffer.data(), buffer.size());
		return writeSize;
#endif
#ifdef _WIN32
		DWORD writeSize = 0;
		BOOL ret = WriteFile(member.hcom, buffer.data(), buffer.size(), &writeSize, 0);
		if (!ret)
		{
			member.checkError();
			return -1;
		}
		return writeSize;
#endif
	}
	return -1;
}
bool BSerialPort::flush() const
{
#ifdef _WIN32
	if (isOpened())
	{
		BOOL ret = FlushFileBuffers(member.hcom);
	}
#endif
	return true;
}
